package control_plane

/**
 * @Description 上游请求处理
 * @Author 007lz
 * @Date 2024/7/10 下午4:45
 **/
import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"time"

	"gitee.com/git-lz/twelve/common/consts"
	"gitee.com/git-lz/twelve/common/dto/response"
	"gitee.com/git-lz/twelve/common/merrors"
	"gitee.com/git-lz/twelve/common/utils"
	"gitee.com/git-lz/twelve/common/zaplog"
	"gitee.com/git-lz/twelve/logic/action"
	"gitee.com/git-lz/twelve/logic/condition"
	"gitee.com/git-lz/twelve/model/task"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"
)

// Entry
// @Description 上游请求处理入口
// @Param ctx 上下文
// @return response结构体
func (c *ControlPlane) Entry(ctx context.Context, request *http.Request, path string) *response.Response {
	start := time.Now()

	resp := response.NewResponse()

	// 准备工作
	if err := c.onPrepare(ctx, request, path); err != nil {
		zaplog.Errorf(ctx, consts.DLTagTaskPrepareFailed, start, zap.Error(err))
		return resp.WithMsg(merrors.ErrnoGetConfigFailed, err.Error())
	}

	// 任务处理
	if err := c.doTask(ctx); err != nil {
		zaplog.Errorf(ctx, consts.DLTagTaskExecuteFailed, start, zap.Error(err))
		return resp.WithMsg(merrors.ErrnoTaskExecuteFailed, err.Error())
	}

	response, err := c.TaskContext.GetResponse(ctx)
	if err != nil {
		zaplog.Errorf(ctx, consts.DLTagTaskExecuteFailed, start, zap.Error(err))
		return resp.WithMsg(merrors.ErrnoTaskExecuteFailed, err.Error())
	}
	
	return resp.WithData(response)
}

// onPrepare
// 完成请求处理前的准备工作，主要完成任务配置的获取以及任务上下文数据的初始化工作
func (c *ControlPlane) onPrepare(ctx context.Context, request *http.Request, path string) error {
	start := time.Now()

	if err := c.setTask(ctx, request, path); err != nil {
		zaplog.Errorf(ctx, consts.DLTagTaskNotFind, start, zap.Error(err))
		return err
	}

	if err := c.getTaskContext(ctx, request, path); err != nil {
		zaplog.Errorf(ctx, consts.DLTagTaskExecuteFailed, start, zap.Error(err))
		return err
	}

	return nil
}

// doTask
// 任务的处理
func (c *ControlPlane) doTask(ctx context.Context) error {
	start := time.Now()

	task := c.Task

	var visited []string
	errG, _ := errgroup.WithContext(ctx)

	var dealNodeFunc = func(nodeId string) error {
		nodeInfo, ok := task.Nodes[nodeId]
		if !ok {
			return errors.New(fmt.Sprintf("nodeId=(%v) not exits!", nodeId))
		}

		// 避免重复处理
		if utils.IsInArray(nodeId, visited) {
			zaplog.Info(ctx, consts.DLTagSuccess, start,
				zap.String("msg", fmt.Sprintf("nodeId=(%v) had been deal!", nodeId)))
			return nil
		}

		// 异步处理
		if nodeInfo.Async {
			errG.Go(func() error {
				return c.dealEachNode(ctx, nodeId)
			})
		} else { // 同步处理
			return c.doTaskBySync(ctx, nodeId)
		}

		visited = append(visited, nodeId)
		return nil
	}

	if err := utils.BFS(task.HeadNodeId, task.Edge.FromToNodeIds, dealNodeFunc); err != nil {
		return err
	}

	// 获取异步执行结果
	if err := errG.Wait(); err != nil {
		zaplog.Errorf(ctx, consts.DLTagTaskNotFind, start, zap.Error(err))
		return err
	}

	return nil
}

// doTaskBySync
// 同步处理node
func (c *ControlPlane) doTaskBySync(ctx context.Context, nodeId string) error {
	start := time.Now()

	// TODO: 封装带recover的errgroup
	errG, _ := errgroup.WithContext(ctx)
	errG.Go(func() error {
		defer func() {
			if r := recover(); r != nil {
				zaplog.Errorf(ctx, consts.DLTagTaskExecuteFailed, start, zap.Any("recover", r))
			}
		}()

		return c.dealEachNode(ctx, nodeId)
	})

	if err := errG.Wait(); err != nil {
		return err
	}

	return nil
}

// dealEachNode
// 处理每个Node
func (c *ControlPlane) dealEachNode(ctx context.Context, nodeId string) error {
	start := time.Now()

	nodeInfo, ok := c.Task.Nodes[nodeId]
	if !ok {
		return errors.New(fmt.Sprintf("nodeId=(%v) not exist", nodeId))
	}

	for _, element := range nodeInfo.Elements {
		// condition校验
		if err := condition.CheckCondition(ctx, element.ConditionInfo, c.TaskContext); err != nil {
			zaplog.Errorf(ctx, consts.DLTagConditionCheckFailed, start, zap.Error(err))
			return err
		}

		// action执行,只有condition校验通过，才会执行action
		execActionParams := &task.ExecActionParams{
			NodeId:      nodeId,
			ActionInfo:  element.ActionInfo,
			TaskContext: c.TaskContext,
		}
		if err := action.Process(ctx, execActionParams); err != nil {
			zaplog.Errorf(ctx, consts.DLTagActionExecuteFailed, start, zap.Error(err))
			return err
		}
	}

	return nil
}
